#pragma once
#include <iostream>
#include <exception>

using namespace std;

//=== ArrayIteratorException declaration and implementation ==============

class ArrayIteratorException
{
    friend std::ostream &operator <<(std::ostream &os, const ArrayIteratorException &e)
    {
        os << e.message.c_str();
        return os;
    }

public:
    ArrayIteratorException(const std::string &message) : message(message) {}
private:
    std::string message;
};

//=== ArrayIterator declaration and implementation ==============

template <typename TYPE>
class ArrayIterator
{
    friend ostream &operator <<(ostream &os, const ArrayIterator<TYPE> &array)
    {
        os << array.current;
        return os;
    }

public:
    ArrayIterator(TYPE *current, TYPE *beyondLast) :
      current(current),
          beyondLast(beyondLast)
      {
      }

      TYPE& operator *()
      {
          if (current == beyondLast)
              throw ArrayIteratorException("Failed to dereference beyond last array element");
          return *current;
      }

      ArrayIterator<TYPE> operator ++()
      {
          if (current == beyondLast)
              throw ArrayIteratorException("Failed to increment iterator beyond last array element");
          current++;
          return *this;
      }

      bool operator ==(const ArrayIterator<TYPE> &other)
      {
          return current == other.current;
      } 

      bool operator !=(const ArrayIterator<TYPE> &other)
      {
          return !(*this == other);
      }

private:
    TYPE *current;
    TYPE *beyondLast;
};

//=== Array declaration ==============

template <typename TYPE>
class Array
{
    friend ostream &operator <<(ostream &os, const Array<TYPE> &arr)
    {
        os << "{";
        for (int i = 0; i < arr.length(); i++)
            os << arr.buffer[i] << (i < arr.length()-1 ? ", " : "");
        os << "}";
        return os;
    }

public:
    Array<TYPE>(int size, TYPE *arr = 0);
    ~Array<TYPE>();
    Array<TYPE>& Array<TYPE>::operator= (const Array& other);
    int length() const { return size; }
    ArrayIterator<TYPE> begin() {return ArrayIterator<TYPE>(buffer, buffer + size);}
    ArrayIterator<TYPE> end() {return ArrayIterator<TYPE>(buffer + size, buffer + size);}
private:
    TYPE *buffer;
    int size;
};

//=== Array inline implementations ==============

template <typename TYPE>
Array<TYPE>::Array(int size, TYPE *arr) :
    size(size),
    buffer(new TYPE[size])
{
    for (int i = 0; i < size; i++)
        buffer[i] = arr ? arr[i] : TYPE();
}

template <typename TYPE>
Array<TYPE>::~Array()
{
    delete[] buffer;
}

template <typename TYPE>
Array<TYPE>& Array<TYPE>::operator= (const Array<TYPE>& other)
{
    if (this != &other)
    {
        delete[] buffer;
        buffer = new TYPE[other.size];
        size = other.size;
        for (int i = 0; i < size; i++)
            buffer[i] = other.buffer[i];
    }
    return *this;
}
